﻿using MongoDB.Bson;
using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Linq;
using System.Linq.Expressions;
using System.Diagnostics;
using System.Text.RegularExpressions;
using Doolin.MongoRepository.Commons;

namespace Doolin.MongoRepository
{
  public class MongoRepository : IMongoRepositoryAsync,IMongoRepository
  {
    private readonly MongoDbContext _mongoDbContext = null;
    public MongoRepository(MongoDbContext mongoDbContext)
    {
      _mongoDbContext = mongoDbContext;
    }
    public MongoRepository(string connectionString, string databaseName)
    {
      _mongoDbContext = new MongoDbContext(connectionString, databaseName);
    }

    #region IMongoRepositoryAsync

    #region Get
    public async Task<TEntity> GetOneAsync<TEntity>(string id) where TEntity : IMongoEntity, new()
    {
      var filter = Builders<TEntity>.Filter.Eq("Id", ObjectId.Parse(id));
      return await GetOneAsync<TEntity>(filter);
    }
    public async Task<TEntity> GetOneAsync<TEntity>(ObjectId id) where TEntity : IMongoEntity, new()
    {
      var filter = Builders<TEntity>.Filter.Eq("Id", id);
      return await GetOneAsync<TEntity>(filter);
    }
    public async Task<TEntity> GetOneAsync<TEntity>(FilterDefinition<TEntity> filter) where TEntity : IMongoEntity, new()
    {
      TEntity res = default(TEntity);
      try
      {
        var collection = GetCollection<TEntity>();
        return await collection.Find(filter).SingleOrDefaultAsync();
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
        return res;
      }
    }
    public async Task<List<TEntity>> GetManyAsync<TEntity>(IEnumerable<string> ids) where TEntity : IMongoEntity, new()
    {
      try
      {
        var collection = GetCollection<TEntity>();
        var filter = Builders<TEntity>.Filter.Eq("Id", ids);
        return await GetManyAsync<TEntity>(filter);
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
        return new List<TEntity>();
      }
    }
    public async Task<List<TEntity>> GetManyAsync<TEntity>(FilterDefinition<TEntity> filter) where TEntity : IMongoEntity, new()
    {
      try
      {
        var collection = GetCollection<TEntity>();
        return  await collection.Find(filter).ToListAsync();
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
        return new List<TEntity>();
      }
    }
    public IFindFluent<TEntity, TEntity> FindCursorAsync<TEntity>(FilterDefinition<TEntity> filter) where TEntity : IMongoEntity, new()
    {
      var collection = GetCollection<TEntity>();
      var cursor = collection.Find(filter);
      return cursor;
    }
    public async Task<List<TEntity>> GetAllAsync<TEntity>() where TEntity : IMongoEntity, new()
    {
      try
      {
        var collection = GetCollection<TEntity>();
        return  await collection.Find(new BsonDocument()).ToListAsync();
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
        return new List<TEntity>();
      }
    }
    public async Task<bool> ExistsAsync<TEntity>(string id) where TEntity : IMongoEntity, new()
    {
      var collection = GetCollection<TEntity>();
      var query = new BsonDocument("Id", id);
      var cursor = collection.Find(query);
      var count = await cursor.CountDocumentsAsync();
      return (count > 0);
    }
    public async Task<long> CountAsync<TEntity>(string id) where TEntity : IMongoEntity, new()
    {
      var filter = new FilterDefinitionBuilder<TEntity>().Eq("Id", id);
      return await CountAsync<TEntity>(filter);
    }
    public async Task<long> CountAsync<TEntity>(FilterDefinition<TEntity> filter) where TEntity : IMongoEntity, new()
    {
      var collection = GetCollection<TEntity>();
      var cursor = collection.Find(filter);
      var count = await cursor.CountDocumentsAsync();
      return count;
    }
    #endregion Get

    #region Create
    public async Task AddOneAsync<TEntity>(TEntity item) where TEntity : IMongoEntity, new()
    {
      try
      {
        var collection = GetCollection<TEntity>();
        await collection.InsertOneAsync(item);
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
      }
    }
    #endregion Create

    #region Delete
    public async Task DeleteOneAsync<TEntity>(string id) where TEntity : IMongoEntity, new()
    {
      var filter = new FilterDefinitionBuilder<TEntity>().Eq("Id", id);
      await DeleteOneAsync<TEntity>(filter);
    }
    public async Task DeleteOneAsync<TEntity>(FilterDefinition<TEntity> filter) where TEntity : IMongoEntity, new()
    {
      try
      {
        var collection = GetCollection<TEntity>();
        await collection.DeleteOneAsync(filter);
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
      }
    }
    public async Task<long> DeleteManyAsync<TEntity>(IEnumerable<string> ids) where TEntity : IMongoEntity, new()
    {
      var filter = new FilterDefinitionBuilder<TEntity>().In("Id", ids);
      return await DeleteManyAsync<TEntity>(filter);
    }
    public async Task<long> DeleteManyAsync<TEntity>(FilterDefinition<TEntity> filter) where TEntity : IMongoEntity, new()
    {
      try
      {
        var collection = GetCollection<TEntity>();
        var res = await collection.DeleteManyAsync(filter);
        return res.DeletedCount;
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
        return 0;
      }

    }
    #endregion Delete

    #region Update
    public async Task UpdateOneAsync<TEntity>(string id, UpdateDefinition<TEntity> update) where TEntity : IMongoEntity, new()
    {
      var filter = new FilterDefinitionBuilder<TEntity>().Eq("Id", id);
      await UpdateOneAsync<TEntity>(filter, update);
    }
    public async Task UpdateOneAsync<TEntity>(FilterDefinition<TEntity> filter, UpdateDefinition<TEntity> update) where TEntity : IMongoEntity, new()
    {
      try
      {
        var collection = GetCollection<TEntity>();
        await collection.UpdateOneAsync(filter, update);
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
      }
    }
    public async Task<long> UpdateManyAsync<TEntity>(IEnumerable<string> ids, UpdateDefinition<TEntity> update) where TEntity : IMongoEntity, new()
    {
      var filter = new FilterDefinitionBuilder<TEntity>().In("Id", ids);
      return await UpdateManyAsync<TEntity>(filter, update);
    }
    public async Task<long> UpdateManyAsync<TEntity>(FilterDefinition<TEntity> filter, UpdateDefinition<TEntity> update) where TEntity : IMongoEntity, new()
    {
      try
      {
        var collection = GetCollection<TEntity>();
        var res = await collection.UpdateManyAsync(filter, update);
        return res.ModifiedCount;
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
        return 0;
      }
    }
    #endregion Update

    #region Find And Update
    public async Task<TEntity> GetAndUpdateOne<TEntity>(FilterDefinition<TEntity> filter, UpdateDefinition<TEntity> update, FindOneAndUpdateOptions<TEntity, TEntity> options) where TEntity : IMongoEntity, new()
    {
      try
      {
        var collection = GetCollection<TEntity>();
       return await collection.FindOneAndUpdateAsync(filter, update, options);
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
        return default(TEntity);
      }
    }
    #endregion Find And Update

    #endregion

    #region IMongoRepository
    public IMongoCollection<T> GetCollection<T>() where T : IMongoEntity, new()
    {
      return _mongoDbContext.GetCollection<T>();
    }
    public void Delete<T>(Expression<Func<T, bool>> expression) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      collection.DeleteOne<T>(expression);
    }
    public T GetOne<T>(Expression<Func<T, bool>> expression) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      return collection.Find<T>(expression).FirstOrDefault();
    }
    public T GetOne<T>(FilterDefinition<T> filter) where T : IMongoEntity, new()
    {
      T res = default(T);
      try
      {
        var collection = GetCollection<T>();
        return collection.Find(filter).SingleOrDefault();
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
        return res;
      }
    }
    public List<T> All<T>() where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      return collection.Find(new BsonDocument()).ToList();
    }
    public List<T> All<T>(int page, int pageSize) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      return collection.Find(e => true).Skip((page - 1) * pageSize).Limit(pageSize).ToList();
    }
    public List<T> All<T>(Expression<Func<T, bool>> expression,int page, int pageSize) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      return collection.Find(expression).Skip((page - 1) * pageSize).Limit(pageSize).ToList();
    }
    public List<T> All<T>(FilterDefinition<T> filter, int page, int pageSize) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      return collection.Find(filter).Skip((page - 1) * pageSize).Limit(pageSize).ToList();
    }
    public List<T> All<T>(FilterDefinition<T> filter,SortDefinition<T> sort, int page, int pageSize) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      return collection.Find(filter).Sort(sort).Skip((page - 1) * pageSize).Limit(pageSize).ToList();
    }
    public T Add<T>(T item) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      collection.InsertOne(item);
      return item;
    }
    public long AddRange<T>(IEnumerable<T> items) where T : IMongoEntity, new()
    {
      int ret = 0;
      foreach (T item in items)
      {
        try
        {
          Add(item);
          ret++;
        }
        catch (Exception ex){
          Debug.WriteLine(ex.Message);
        }
      }
      return ret;
    }
    public void Update<T>(Expression<Func<T, bool>> expression, UpdateDefinition<T> update, bool upsert=false) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      UpdateOptions op = new UpdateOptions { IsUpsert = upsert };
      collection.UpdateOne<T>(expression, update, op);
    }
    public void Update<T>(FilterDefinition<T> filter, UpdateDefinition<T> update, bool upsert=false) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      UpdateOptions op = new UpdateOptions { IsUpsert = upsert };
      collection.UpdateOne(filter, update, op);
    }
    public void Update<T>(T item) where T : IMongoEntity, new()
    {
      var idFilter = Builders<T>.Filter.Eq(e => e.Id, item.Id);
      var collection = GetCollection<T>();
      collection.ReplaceOne(idFilter, item);
    }
    public async Task UpdateAsync<T>(T item) where T : IMongoEntity, new()
    {
      var idFilter = Builders<T>.Filter.Eq(e => e.Id, item.Id);
      var collection = GetCollection<T>();
      await collection.ReplaceOneAsync(idFilter, item);
    }
    public List<T> GetMany<T>(Expression<Func<T, bool>> expression) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      return collection.Find<T>(expression).ToList();
    }
    public List<T> GetMany<T>(FilterDefinition<T> filter) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      return collection.Find<T>(filter).ToList();
    }
    public long Count<T>(FilterDefinition<T> filter) where T : IMongoEntity, new()
    {
      var collection = GetCollection<T>();
      var cursor = collection.Find(filter);
      var count = cursor.CountDocuments();
      return count;

    }
    #endregion
  }
}
